Программирование сетевых приложений

Концепция распределенной обработки данных и технологии удаленной обработки данных

Программирование сетевых приложений

Содержание лекции

  • Понятие и архитектура распределенной системы
  • Протоколы и программная реализация удаленного вызова процедур
  • Модель RPC: контракт, прокси, сериализация, ошибки
  • Объектно-ориентированные вызовы удаленных методов
  • Архитектура решений, основанных на Web Services
  • Протоколы и стандарты
  • Публикация и развертывание служб
  • Практические примеры на C++ и Qt
Концепция распределенной обработки данных
Программирование сетевых приложений

Понятие распределенной системы

Определение

Распределенная система — это совокупность независимых компьютеров, представляемых пользователю как единая координированная система.

Основные характеристики

  • Автономность компонентов — каждый узел может работать независимо
  • Согласованность — единое представление данных
  • Масштабируемость — возможность добавления новых узлов
  • Отказоустойчивость — продолжение работы при сбоях
  • Прозрачность — скрытие распределенности от пользователя
Концепция распределенной обработки данных
Программирование сетевых приложений

Архитектурные стили распределенных систем

Клиент-серверная архитектура

  • Тонкий клиент — минимальная логика на клиенте
  • Толстый клиент — значительная часть логики на клиенте
  • Многоуровневая архитектура — presentation, business, data layers

Одноранговая (P2P) архитектура

  • Все узлы равноправны
  • Отсутствует централизованный сервер
  • Высокая отказоустойчивость
Концепция распределенной обработки данных
Программирование сетевых приложений

Удаленный вызов процедур (RPC)

Концепция RPC

Позволяет программе вызывать процедуры на удаленном компьютере так, как если бы они были локальными.

Главная идея

  • Клиент работает с локальной функцией или объектом-прокси
  • Прокси преобразует вызов в сетевое сообщение
  • Сервер принимает сообщение и вызывает реальную процедуру
  • Результат или ошибка возвращаются клиенту как ответ
Концепция распределенной обработки данных
Программирование сетевых приложений

Зачем нужен RPC

Типовые задачи

  • Вынести вычисления или бизнес-логику на сервер
  • Организовать единый доступ к общим данным
  • Разделить систему на независимые сервисы
  • Скрыть внутреннюю реализацию сервера за контрактом
  • Использовать разные языки и платформы в одной системе

Где применяется

  • Банковские и корпоративные системы
  • Микросервисная архитектура
  • Клиент-серверные настольные приложения
  • Облачные сервисы и внутренние API
  • Высокопроизводительные системы на gRPC/Thrift
Концепция распределенной обработки данных
Программирование сетевых приложений

Механизм RPC-вызова

Последовательность выполнения

  1. Клиент вызывает метод локального прокси
  2. Прокси выполняет маршаллинг параметров
  3. Запрос передается по сети на сервер
  4. Серверный диспетчер находит нужную процедуру
  5. Сервер выполняет процедуру и формирует результат
  6. Результат сериализуется и отправляется клиенту
  7. Клиентский прокси возвращает результат вызывающему коду

Термины

  • Stub / proxy — локальный представитель удаленной процедуры
  • Marshalling — упаковка параметров в формат передачи
  • Unmarshalling — восстановление параметров из сообщения
  • Dispatcher — выбор серверного обработчика по имени метода
Концепция распределенной обработки данных
Программирование сетевых приложений

Контракт RPC

Что описывает контракт

  • Имена доступных методов
  • Типы и порядок параметров
  • Тип возвращаемого значения
  • Возможные ошибки
  • Версию интерфейса

Зачем нужен контракт

  • Клиент и сервер одинаково понимают формат сообщений
  • Ошибки несовместимости обнаруживаются раньше
  • Можно генерировать клиентские и серверные заглушки
  • Упрощается документирование и тестирование API
Концепция распределенной обработки данных
Программирование сетевых приложений

Чем RPC отличается от REST

RPC

  • Ориентирован на действия и методы
  • Пример: calculator.add(5, 3)
  • URL часто один: /rpc или /jsonrpc
  • Метод операции задается в теле запроса

REST

  • Ориентирован на ресурсы и их состояния
  • Пример: GET /users/10, POST /orders
  • Операция выражается HTTP-методом
  • Ресурс выражается URI
Концепция распределенной обработки данных
Программирование сетевых приложений

Семантика удаленного вызова

Почему это важно

Локальный вызов либо выполняется, либо процесс получает исключение. В RPC возможны промежуточные состояния: запрос ушел, но ответ потерялся; сервер выполнил операцию, но клиент получил таймаут.

Варианты семантики

  • At most once — операция выполняется не более одного раза
  • At least once — операция будет повторяться до успешного ответа
  • Exactly once — желаемая, но трудно достижимая гарантия

Практические механизмы

  • Уникальный id запроса
  • Таймауты и повторные попытки
  • Идемпотентные операции
  • Дедупликация запросов на сервере
Концепция распределенной обработки данных
Программирование сетевых приложений

Ошибки в RPC

Категории ошибок

  • Ошибка транспорта: нет соединения, таймаут, DNS, TLS
  • Ошибка протокола: некорректный JSON/XML, неверная версия
  • Ошибка контракта: неизвестный метод, неверные параметры
  • Ошибка приложения: деление на ноль, нет прав, объект не найден
  • Ошибка инфраструктуры: перегрузка, отказ БД, недоступность зависимости

Что должен делать клиент

  • Различать сетевые и прикладные ошибки
  • Ограничивать время ожидания ответа
  • Не повторять небезопасные операции без защиты
  • Логировать id запроса и причину сбоя
Концепция распределенной обработки данных
Программирование сетевых приложений

Синхронный и асинхронный RPC

Синхронный вызов

  • Код клиента ожидает ответ перед продолжением работы
  • Проще читать и отлаживать
  • Может блокировать интерфейс или рабочий поток

Асинхронный вызов

  • Клиент отправляет запрос и продолжает работу
  • Результат приходит через callback, signal/slot или future
  • Лучше подходит для GUI и высоконагруженных сервисов

В Qt

  • Для GUI предпочтительны асинхронные запросы
  • Сигналы finished, readyRead, errorOccurred позволяют не блокировать event loop
Концепция распределенной обработки данных
Программирование сетевых приложений

Ограничения прозрачности RPC

Почему удаленный вызов не равен локальному

  • Сетевая задержка на порядки выше вызова функции в памяти
  • Сервер может быть недоступен
  • Сообщение может потеряться или прийти повторно
  • Данные нужно сериализовать и копировать
  • Версии клиента и сервера могут отличаться

Вывод

RPC удобно имитирует локальный вызов, но проектировать его нужно как сетевое взаимодействие с отказами, задержками и ограниченными гарантиями.

Концепция распределенной обработки данных
Программирование сетевых приложений

Пример клиентского прокси RPC

// Интерфейс удаленной процедуры
class CalculatorInterface {
public:
    virtual int add(int a, int b) = 0;
    virtual int multiply(int a, int b) = 0;
    virtual ~CalculatorInterface() = default;
};

// Клиентский прокси
class CalculatorProxy : public CalculatorInterface {
private:
    QTcpSocket* socket;
    
public:
    int add(int a, int b) override {
        // Сериализация запроса
        QJsonObject request;
        request["method"] = "add";
        request["params"] = QJsonArray{a, b};
        request["id"] = generateId();
        
        // Отправка запроса
        sendRequest(request);
        
        // Получение ответа
        QJsonObject response = receiveResponse();
        return response["result"].toInt();
    }
};
Концепция распределенной обработки данных
Программирование сетевых приложений

Протокол XML-RPC

Структура запроса

<?xml version="1.0"?>
<methodCall>
    <methodName>calculator.add</methodName>
    <params>
        <param><value><int>5</int></value></param>
        <param><value><int>3</int></value></param>
    </params>
</methodCall>

Структура ответа

<?xml version="1.0"?>
<methodResponse>
    <params>
        <param><value><int>8</int></value></param>
    </params>
</methodResponse>
Концепция распределенной обработки данных
Программирование сетевых приложений

Реализация XML-RPC клиента на Qt

#include <QtNetwork>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>

class XmlRpcClient : public QObject {
    Q_OBJECT
    
private:
    QNetworkAccessManager* manager;
    QString serverUrl;
    
public:
    XmlRpcClient(const QString& url, QObject* parent = nullptr) 
        : QObject(parent), serverUrl(url) {
        manager = new QNetworkAccessManager(this);
    }
    
    void callMethod(const QString& methodName, 
                   const QVariantList& params) {
        // Создание XML запроса
        QByteArray requestData = createXmlRequest(methodName, params);
        
        // Отправка POST запроса
        QNetworkRequest request(QUrl(serverUrl));
        request.setHeader(QNetworkRequest::ContentTypeHeader, 
                         "text/xml");
        
        QNetworkReply* reply = manager->post(request, requestData);
        
        connect(reply, &QNetworkReply::finished, [=]() {
            if (reply->error() == QNetworkReply::NoError) {
                QVariant result = parseXmlResponse(reply->readAll());
                emit methodCalled(result);
            } else {
                emit errorOccurred(reply->errorString());
            }
            reply->deleteLater();
        });
    }
    
private:
    QByteArray createXmlRequest(const QString& method, 
                               const QVariantList& params);
    QVariant parseXmlResponse(const QByteArray& response);
    
signals:
    void methodCalled(const QVariant& result);
    void errorOccurred(const QString& error);
};
Концепция распределенной обработки данных
Программирование сетевых приложений

Реализация XML-RPC сервера на Qt

#include <QTcpServer>
#include <QTcpSocket>
#include <QXmlStreamReader>

class XmlRpcServer : public QTcpServer {
    Q_OBJECT
    
private:
    QMap<QString, std::function<QVariant(QVariantList)>> methods;
    
public:
    XmlRpcServer(QObject* parent = nullptr) : QTcpServer(parent) {
        // Регистрация методов
        registerMethod("add", [](QVariantList params) {
            return params[0].toInt() + params[1].toInt();
        });
        
        registerMethod("multiply", [](QVariantList params) {
            return params[0].toInt() * params[1].toInt();
        });
    }
    
    void registerMethod(const QString& name, 
                       std::function<QVariant(QVariantList)> method) {
        methods[name] = method;
    }
    
protected:
    void incomingConnection(qintptr socketDescriptor) override {
        QTcpSocket* socket = new QTcpSocket(this);
        socket->setSocketDescriptor(socketDescriptor);
        
        connect(socket, &QTcpSocket::readyRead, [=]() {
            QByteArray request = socket->readAll();
            QByteArray response = processRequest(request);
            socket->write(response);
            socket->disconnectFromHost();
        });
    }
    
private:
    QByteArray processRequest(const QByteArray& request);
};
Концепция распределенной обработки данных
Программирование сетевых приложений

JSON-RPC протокол

Преимущества JSON-RPC

  • Компактность — меньший размер сообщений по сравнению с XML
  • Производительность — быстрее парсится
  • Читаемость — легче воспринимается человеком
  • Поддержка типов — хорошая поддержка в Qt
Концепция распределенной обработки данных
Программирование сетевых приложений

Пример JSON-RPC запроса

{
    "jsonrpc": "2.0",
    "method": "calculator.divide",
    "params": [10, 2],
    "id": 1
}

Пример JSON-RPC ответа

{
    "jsonrpc": "2.0",
    "result": 5,
    "id": 1
}
Концепция распределенной обработки данных
Программирование сетевых приложений

Реализация JSON-RPC на Qt

#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>

class JsonRpcClient : public QObject {
    Q_OBJECT
    
private:
    QNetworkAccessManager* manager;
    QString serverUrl;
    int requestId;
    
public:
    JsonRpcClient(const QString& url, QObject* parent = nullptr)
        : QObject(parent), serverUrl(url), requestId(0) {
        manager = new QNetworkAccessManager(this);
    }
    
    void callMethod(const QString& method, const QJsonArray& params) {
        // Формирование запроса
        QJsonObject request;
        request["jsonrpc"] = "2.0";
        request["method"] = method;
        request["params"] = params;
        request["id"] = ++requestId;
        
        QJsonDocument doc(request);
        QByteArray data = doc.toJson();
        
        // Отправка запроса
        QNetworkRequest netRequest(QUrl(serverUrl));
        netRequest.setHeader(QNetworkRequest::ContentTypeHeader, 
                            "application/json");
        
        QNetworkReply* reply = manager->post(netRequest, data);
        
        connect(reply, &QNetworkReply::finished, [=]() {
            if (reply->error() == QNetworkReply::NoError) {
                QJsonDocument responseDoc = 
                    QJsonDocument::fromJson(reply->readAll());
                QJsonObject response = responseDoc.object();
                
                if (response.contains("result")) {
                    emit methodSuccess(response["result"]);
                } else if (response.contains("error")) {
                    emit methodError(response["error"].toObject());
                }
            }
            reply->deleteLater();
        });
    }
    
signals:
    void methodSuccess(const QJsonValue& result);
    void methodError(const QJsonObject& error);
};
Концепция распределенной обработки данных
Программирование сетевых приложений

Современные RPC-фреймворки

gRPC

  • Использует HTTP/2 как транспорт
  • Описывает контракт в .proto-файлах
  • Кодирует данные через Protocol Buffers
  • Поддерживает unary-вызовы и streaming
  • Хорошо подходит для микросервисов

Apache Thrift

  • Использует собственный IDL
  • Поддерживает разные языки и транспорты
  • Часто применяется во внутренних сервисах

Когда выбирать

  • Нужен строгий контракт между сервисами
  • Важны производительность и компактные сообщения
  • Требуется генерация клиентского и серверного кода
  • API используется преимущественно внутри системы
Концепция распределенной обработки данных
Программирование сетевых приложений

Объектно-ориентированные вызовы удаленных методов

Концепция удаленных объектов

  • Удаленные объекты представляются локальными прокси
  • Прозрачный доступ к методам удаленных объектов
  • Автоматическая сериализация/десериализация параметров
  • Состояние объекта хранится на сервере или синхронизируется с клиентом
  • Клиент работает не с самим объектом, а с его удаленной ссылкой
Концепция распределенной обработки данных
Программирование сетевых приложений

Пример удаленного объекта

// Интерфейс удаленного объекта
class IRemoteCalculator : public QObject {
    Q_OBJECT
    
public:
    virtual ~IRemoteCalculator() = default;
    
public slots:
    virtual int add(int a, int b) = 0;
    virtual int subtract(int a, int b) = 0;
    virtual double divide(double a, double b) = 0;
};

// Клиентская реализация
class RemoteCalculatorClient : public IRemoteCalculator {
    Q_OBJECT
    
private:
    QTcpSocket* socket;
    
public:
    RemoteCalculatorClient(QTcpSocket* sock, QObject* parent = nullptr)
        : IRemoteCalculator(parent), socket(sock) {
        connect(socket, &QTcpSocket::readyRead, 
                this, &RemoteCalculatorClient::onReadyRead);
    }
    
    int add(int a, int b) override {
        return invokeRemoteMethod("add", QVariantList{a, b}).toInt();
    }
    
private:
    QVariant invokeRemoteMethod(const QString& method, 
                               const QVariantList& args);
    void onReadyRead();
};
Концепция распределенной обработки данных
Программирование сетевых приложений

Система удаленных вызовов на Qt

// Базовый класс для удаленных объектов
class RemoteObject : public QObject {
    Q_OBJECT
    
protected:
    QString objectName;
    
public:
    RemoteObject(const QString& name, QObject* parent = nullptr)
        : QObject(parent), objectName(name) {}
    
    virtual QJsonObject toJson() const {
        QJsonObject obj;
        obj["objectName"] = objectName;
        return obj;
    }
    
    virtual void fromJson(const QJsonObject& json) {
        objectName = json["objectName"].toString();
    }
};

// Удаленный калькулятор
class RemoteCalculator : public RemoteObject {
    Q_OBJECT
    
public:
    RemoteCalculator(const QString& name, QObject* parent = nullptr)
        : RemoteObject(name, parent) {}
    
public slots:
    QJsonValue add(const QJsonArray& params) {
        int a = params[0].toInt();
        int b = params[1].toInt();
        return QJsonValue(a + b);
    }
    
    QJsonValue multiply(const QJsonArray& params) {
        int a = params[0].toInt();
        int b = params[1].toInt();
        return QJsonValue(a * b);
    }
};
Концепция распределенной обработки данных
Программирование сетевых приложений

Архитектура Web Services

SOAP (Simple Object Access Protocol)

  • Протокол на основе XML
  • Строгая типизация
  • Поддержка стандартов безопасности
  • WSDL (Web Services Description Language)
Концепция распределенной обработки данных
Программирование сетевых приложений

REST (Representational State Transfer)

  • Простота и легкость
  • Использование стандартных HTTP методов
  • Stateless архитектура
  • Поддержка различных форматов данных
Концепция распределенной обработки данных
Программирование сетевых приложений
// REST клиент на Qt
class RestClient : public QObject {
    Q_OBJECT
    
private:
    QNetworkAccessManager* manager;
    QString baseUrl;
    
public:
    RestClient(const QString& base, QObject* parent = nullptr)
        : QObject(parent), baseUrl(base) {
        manager = new QNetworkAccessManager(this);
    }
    
    void get(const QString& resource) {
        QNetworkRequest request(QUrl(baseUrl + resource));
        QNetworkReply* reply = manager->get(request);
        
        connect(reply, &QNetworkReply::finished, [=]() {
            handleResponse(reply);
        });
    }
    
    void post(const QString& resource, const QJsonObject& data) {
        QNetworkRequest request(QUrl(baseUrl + resource));
        request.setHeader(QNetworkRequest::ContentTypeHeader, 
                         "application/json");
        
        QJsonDocument doc(data);
        QNetworkReply* reply = manager->post(request, doc.toJson());
        
        connect(reply, &QNetworkReply::finished, [=]() {
            handleResponse(reply);
        });
    }
};
Концепция распределенной обработки данных
Программирование сетевых приложений

RESTful сервис на Qt

#include <QTcpServer>
#include <QJsonDocument>
#include <QRegularExpression>

class RestServer : public QTcpServer {
    Q_OBJECT
    
private:
    struct Route {
        QString method;
        QRegularExpression pattern;
        std::function<QJsonObject(const QJsonObject&)> handler;
    };
    
    QList<Route> routes;
    
public:
    RestServer(QObject* parent = nullptr) : QTcpServer(parent) {
        // Регистрация маршрутов
        registerRoute("GET", QRegularExpression("^/api/users$"), 
                     [](const QJsonObject& params) {
            QJsonObject response;
            QJsonArray users;
            
            QJsonObject user1;
            user1["id"] = 1;
            user1["name"] = "John";
            users.append(user1);
            
            response["users"] = users;
            return response;
        });
        
        registerRoute("POST", QRegularExpression("^/api/users$"), 
                     [](const QJsonObject& params) {
            QJsonObject response;
            response["id"] = 123;
            response["status"] = "created";
            return response;
        });
    }
    
    void registerRoute(const QString& method, 
                      const QRegularExpression& pattern,
                      std::function<QJsonObject(const QJsonObject&)> handler) {
        routes.append({method, pattern, handler});
    }
    
protected:
    void incomingConnection(qintptr socketDescriptor) override {
        QTcpSocket* socket = new QTcpSocket(this);
        socket->setSocketDescriptor(socketDescriptor);
        
        connect(socket, &QTcpSocket::readyRead, [=]() {
            QByteArray request = socket->readAll();
            QByteArray response = processHttpRequest(request);
            socket->write(response);
            socket->disconnectFromHost();
        });
    }
    
private:
    QByteArray processHttpRequest(const QByteArray& request);
};
Концепция распределенной обработки данных
Программирование сетевых приложений

Протоколы и стандарты

HTTP/HTTPS

  • GET — получение данных
  • POST — создание ресурсов
  • PUT — обновление ресурсов
  • DELETE — удаление ресурсов
  • PATCH — частичное обновление
Концепция распределенной обработки данных
Программирование сетевых приложений

Форматы данных

  • JSON — JavaScript Object Notation
  • XML — eXtensible Markup Language
  • Protocol Buffers — бинарный формат Google
  • MessagePack — эффективный бинарный формат
Концепция распределенной обработки данных
Программирование сетевых приложений
// Поддержка Protocol Buffers в Qt
class ProtobufSerializer {
public:
    static QByteArray serializeUser(const QJsonObject& user) {
        // Упрощенная реализация
        QByteArray data;
        QDataStream stream(&data, QIODevice::WriteOnly);
        
        stream << user["id"].toInt();
        stream << user["name"].toString();
        stream << user["email"].toString();
        
        return data;
    }
    
    static QJsonObject deserializeUser(const QByteArray& data) {
        QJsonObject user;
        QDataStream stream(data);
        
        int id;
        QString name, email;
        stream >> id >> name >> email;
        
        user["id"] = id;
        user["name"] = name;
        user["email"] = email;
        
        return user;
    }
};
Концепция распределенной обработки данных
Программирование сетевых приложений

Безопасность в распределенных системах

Концепция распределенной обработки данных
Программирование сетевых приложений

Аутентификация и авторизация

class SecurityManager : public QObject {
    Q_OBJECT
    
private:
    QString secretKey;
    int tokenExpiration;
    
public:
    SecurityManager(const QString& key, QObject* parent = nullptr)
        : QObject(parent), secretKey(key), tokenExpiration(3600) {}
    
    QString generateToken(const QString& userId) {
        QJsonObject payload;
        payload["userId"] = userId;
        payload["exp"] = QDateTime::currentDateTime()
                        .addSecs(tokenExpiration).toSecsSinceEpoch();
        
        // Простая JWT-подобная реализация
        QString header = QString::fromUtf8(
            QJsonDocument({{"alg", "HS256"}, {"typ", "JWT"}}).toJson(
                QJsonDocument::Compact).toBase64());
        
        QString payloadStr = QString::fromUtf8(
            QJsonDocument(payload).toJson(
                QJsonDocument::Compact).toBase64());
        
        QString signature = QCryptographicHash::hash(
            (header + "." + payloadStr).toUtf8(),
            QCryptographicHash::Sha256).toHex();
        
        return header + "." + payloadStr + "." + signature;
    }
    
    bool validateToken(const QString& token) {
        QStringList parts = token.split('.');
        if (parts.size() != 3) return false;
        
        // Проверка подписи
        QString expectedSignature = QCryptographicHash::hash(
            (parts[0] + "." + parts[1]).toUtf8(),
            QCryptographicHash::Sha256).toHex();
        
        if (parts[2] != expectedSignature) return false;
        
        // Проверка срока действия
        QJsonDocument payloadDoc = QJsonDocument::fromJson(
            QByteArray::fromBase64(parts[1].toUtf8()));
        
        qint64 exp = payloadDoc.object()["exp"].toDouble();
        return QDateTime::currentDateTime().toSecsSinceEpoch() < exp;
    }
};
Концепция распределенной обработки данных
Программирование сетевых приложений

Шифрование данных

class EncryptionManager {
public:
    static QByteArray encryptAES(const QByteArray& data, 
                                const QByteArray& key) {
        // Упрощенная реализация AES шифрования
        QByteArray encrypted;
        for (int i = 0; i < data.size(); ++i) {
            encrypted.append(data[i] ^ key[i % key.size()]);
        }
        return encrypted;
    }
    
    static QByteArray decryptAES(const QByteArray& encrypted, 
                                const QByteArray& key) {
        // AES дешифрование
        return encryptAES(encrypted, key); // XOR шифрование симметрично
    }
    
    static QSslSocket* createSecureConnection(
        const QString& host, quint16 port) {
        
        QSslSocket* socket = new QSslSocket();
        
        // Настройка SSL/TLS
        connect(socket, &QSslSocket::encrypted, []() {
            qDebug() << "Соединение зашифровано";
        });
        
        connect(socket, &QSslSocket::sslErrors, 
                [](const QList<QSslError>& errors) {
            for (const QSslError& error : errors) {
                qDebug() << "SSL ошибка:" << error.errorString();
            }
        });
        
        socket->connectToHostEncrypted(host, port);
        return socket;
    }
};
Концепция распределенной обработки данных
Программирование сетевых приложений

Публикация и развертывание служб

class ServiceConfiguration {
public:
    struct ServiceInfo {
        QString name;
        QString version;
        QString description;
        QString endpoint;
        quint16 port;
        bool sslEnabled;
    };
    
    static ServiceInfo loadConfiguration(const QString& configFile) {
        ServiceInfo info;
        
        QFile file(configFile);
        if (!file.open(QIODevice::ReadOnly)) {
            qWarning() << "Не удалось открыть файл конфигурации";
            return info;
        }
        
        QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
        QJsonObject config = doc.object();
        
        info.name = config["serviceName"].toString();
        info.version = config["version"].toString("1.0.0");
        info.description = config["description"].toString();
        info.endpoint = config["endpoint"].toString();
        info.port = config["port"].toInt(8080);
        info.sslEnabled = config["sslEnabled"].toBool(false);
        
        return info;
    }
    
    static void saveConfiguration(const ServiceInfo& info, 
                                 const QString& configFile) {
        QJsonObject config;
        config["serviceName"] = info.name;
        config["version"] = info.version;
        config["description"] = info.description;
        config["endpoint"] = info.endpoint;
        config["port"] = info.port;
        config["sslEnabled"] = info.sslEnabled;
        
        QJsonDocument doc(config);
        
        QFile file(configFile);
        if (file.open(QIODevice::WriteOnly)) {
            file.write(doc.toJson());
        }
    }
};
Концепция распределенной обработки данных
Программирование сетевых приложений

Система развертывания

#include <QProcess>
#include <QFileSystemWatcher>

class ServiceDeployment : public QObject {
    Q_OBJECT
    
private:
    QString servicePath;
    QProcess* serviceProcess;
    QFileSystemWatcher* watcher;
    
public:
    ServiceDeployment(const QString& path, QObject* parent = nullptr)
        : QObject(parent), servicePath(path) {
        
        serviceProcess = new QProcess(this);
        watcher = new QFileSystemWatcher(this);
        
        // Наблюдение за изменениями в каталоге службы
        watcher->addPath(servicePath);
        
        connect(watcher, &QFileSystemWatcher::directoryChanged,
                this, &ServiceDeployment::onServiceChanged);
        
        connect(serviceProcess, QOverload<int, QProcess::ExitStatus>::of(
                &QProcess::finished),
                this, &ServiceDeployment::onServiceFinished);
    }
    
    void deploy() {
        // Остановка текущей службы
        stopService();
        
        // Копирование новых файлов
        copyServiceFiles();
        
        // Запуск службы
        startService();
    }
    
    void startService() {
        QString program = servicePath + "/service";
        QStringList arguments;
        arguments << "--config" << servicePath + "/config.json";
        
        serviceProcess->start(program, arguments);
        
        if (serviceProcess->waitForStarted()) {
            emit serviceStarted();
        } else {
            emit errorOccurred("Не удалось запустить службу");
        }
    }
    
    void stopService() {
        if (serviceProcess->state() != QProcess::NotRunning) {
            serviceProcess->terminate();
            if (!serviceProcess->waitForFinished(5000)) {
                serviceProcess->kill();
            }
        }
    }
    
signals:
    void serviceStarted();
    void serviceStopped();
    void errorOccurred(const QString& error);
    
private slots:
    void onServiceChanged(const QString& path) {
        qDebug() << "Служба изменена, перезапуск...";
        deploy();
    }
    
    void onServiceFinished(int exitCode, QProcess::ExitStatus exitStatus) {
        emit serviceStopped();
        if (exitCode != 0) {
            emit errorOccurred(QString("Служба завершилась с кодом %1")
                             .arg(exitCode));
        }
    }
};
Концепция распределенной обработки данных
Программирование сетевых приложений

Мониторинг и логирование

#include <QFile>
#include <QTextStream>
#include <QDateTime>

class ServiceLogger : public QObject {
    Q_OBJECT
    
private:
    QFile logFile;
    QtMsgType logLevel;
    
public:
    ServiceLogger(const QString& logPath, QtMsgType level = QtInfoMsg,
                 QObject* parent = nullptr)
        : QObject(parent), logLevel(level) {
        
        logFile.setFileName(logPath);
        if (logFile.open(QIODevice::WriteOnly | QIODevice::Append)) {
            qInstallMessageHandler(messageHandler);
        }
    }
    
    static void messageHandler(QtMsgType type, 
                              const QMessageLogContext& context,
                              const QString& msg) {
        static ServiceLogger* logger = 
            qobject_cast<ServiceLogger*>(qApp->property("logger").value<QObject*>());
        
        if (!logger) return;
        
        QString timestamp = QDateTime::currentDateTime()
                           .toString("yyyy-MM-dd hh:mm:ss.zzz");
        
        QString logLevel;
        switch (type) {
            case QtDebugMsg: logLevel = "DEBUG"; break;
            case QtInfoMsg: logLevel = "INFO"; break;
            case QtWarningMsg: logLevel = "WARNING"; break;
            case QtCriticalMsg: logLevel = "CRITICAL"; break;
            case QtFatalMsg: logLevel = "FATAL"; break;
        }
        
        QString logMessage = QString("[%1] %2: %3 (%4:%5)\n")
                           .arg(timestamp)
                           .arg(logLevel)
                           .arg(msg)
                           .arg(context.file)
                           .arg(context.line);
        
        QTextStream stream(&logger->logFile);
        stream << logMessage;
        stream.flush();
        
        // Вывод в консоль
        fprintf(stderr, "%s", logMessage.toLocal8Bit().constData());
        
        if (type == QtFatalMsg) {
            abort();
        }
    }
};

class ServiceMonitor : public QObject {
    Q_OBJECT
    
private:
    QTimer* statsTimer;
    QJsonObject currentStats;
    
public:
    ServiceMonitor(QObject* parent = nullptr) : QObject(parent) {
        statsTimer = new QTimer(this);
        statsTimer->setInterval(60000); // 1 минута
        
        connect(statsTimer, &QTimer::timeout,
                this, &ServiceMonitor::collectStatistics);
        
        statsTimer->start();
    }
    
    void updateMetric(const QString& name, const QVariant& value) {
        currentStats[name] = QJsonValue::fromVariant(value);
    }
    
    QJsonObject getStatistics() const {
        return currentStats;
    }
    
signals:
    void statisticsUpdated(const QJsonObject& stats);
    
private slots:
    void collectStatistics() {
        // Сбор системной информации
        currentStats["timestamp"] = QDateTime::currentDateTime().toString(Qt::ISODate);
        currentStats["memoryUsage"] = getMemoryUsage();
        currentStats["cpuUsage"] = getCpuUsage();
        currentStats["activeConnections"] = getActiveConnections();
        
        emit statisticsUpdated(currentStats);
    }
    
private:
    qint64 getMemoryUsage() {
        // Упрощенное получение информации о памяти
        return 0; // Реальная реализация зависит от платформы
    }
    
    double getCpuUsage() {
        // Упрощенное получение информации о CPU
        return 0.0;
    }
    
    int getActiveConnections() {
        // Получение количества активных соединений
        return 0;
    }
};
Концепция распределенной обработки данных
Программирование сетевых приложений

Примеры интеграции

// Пример полноценного распределенного калькулятора
class DistributedCalculator : public QObject {
    Q_OBJECT
    
private:
    JsonRpcClient* client;
    ServiceMonitor* monitor;
    
public:
    DistributedCalculator(const QString& serverUrl, 
                         QObject* parent = nullptr)
        : QObject(parent) {
        
        client = new JsonRpcClient(serverUrl, this);
        monitor = new ServiceMonitor(this);
        
        connect(client, &JsonRpcClient::methodSuccess,
                this, &DistributedCalculator::onCalculationSuccess);
        
        connect(client, &JsonRpcClient::methodError,
                this, &DistributedCalculator::onCalculationError);
        
        connect(monitor, &ServiceMonitor::statisticsUpdated,
                this, &DistributedCalculator::onStatisticsUpdated);
    }
    
    void calculate(const QString& operation, 
                  const QJsonArray& params) {
        monitor->updateMetric("lastOperation", operation);
        monitor->updateMetric("operationsCount", 
                             monitor->getStatistics()["operationsCount"]
                             .toInt() + 1);
        
        client->callMethod(operation, params);
    }
    
    void performComplexCalculation() {
        // Пример цепочки удаленных вызовов
        QJsonArray params1{10, 5};
        client->callMethod("add", params1);
        
        QJsonArray params2{8, 3};
        client->callMethod("multiply", params2);
        
        QJsonArray params3{100, 4};
        client->callMethod("divide", params3);
    }
    
signals:
    void calculationResult(const QString& operation, 
                          double result);
    void calculationError(const QString& error);
    void systemStatistics(const QJsonObject& stats);
    
private slots:
    void onCalculationSuccess(const QJsonValue& result) {
        double value = result.toDouble();
        emit calculationResult("operation", value);
    }
    
    void onCalculationError(const QJsonObject& error) {
        QString errorMsg = error["message"].toString();
        emit calculationError(errorMsg);
    }
    
    void onStatisticsUpdated(const QJsonObject& stats) {
        emit systemStatistics(stats);
    }
};

// Пример использования
int main(int argc, char *argv[]) {
    QCoreApplication app(argc, argv);
    
    DistributedCalculator calculator("http://localhost:8080/jsonrpc");
    
    QObject::connect(&calculator, &DistributedCalculator::calculationResult,
                    [](const QString& op, double result) {
        qDebug() << "Результат:" << result;
    });
    
    QObject::connect(&calculator, &DistributedCalculator::calculationError,
                    [](const QString& error) {
        qDebug() << "Ошибка:" << error;
    });
    
    // Простые вычисления
    calculator.calculate("add", QJsonArray{15, 25});
    calculator.calculate("multiply", QJsonArray{7, 8});
    
    // Сложные вычисления
    calculator.performComplexCalculation();
    
    return app.exec();
}
Концепция распределенной обработки данных
Программирование сетевых приложений

Масштабируемость и производительность

class LoadBalancer : public QObject {
    Q_OBJECT
    
private:
    struct ServerInfo {
        QString url;
        int load;
        bool available;
        QDateTime lastCheck;
    };
    
    QList<ServerInfo> servers;
    QTimer* healthCheckTimer;
    
public:
    LoadBalancer(QObject* parent = nullptr) : QObject(parent) {
        healthCheckTimer = new QTimer(this);
        healthCheckTimer->setInterval(30000); // 30 секунд
        
        connect(healthCheckTimer, &QTimer::timeout,
                this, &LoadBalancer::performHealthCheck);
        
        // Добавление серверов
        addServer("http://server1:8080");
        addServer("http://server2:8080");
        addServer("http://server3:8080");
        
        healthCheckTimer->start();
    }
    
    void addServer(const QString& url) {
        ServerInfo info;
        info.url = url;
        info.load = 0;
        info.available = true;
        info.lastCheck = QDateTime::currentDateTime();
        
        servers.append(info);
    }
    
    QString selectServer() {
        // Алгоритм наименьшей нагрузки
        ServerInfo* bestServer = nullptr;
        int minLoad = INT_MAX;
        
        for (ServerInfo& server : servers) {
            if (server.available && server.load < minLoad) {
                minLoad = server.load;
                bestServer = &server;
            }
        }
        
        if (bestServer) {
            bestServer->load++;
            return bestServer->url;
        }
        
        return QString(); // Нет доступных серверов
    }
    
private slots:
    void performHealthCheck() {
        for (ServerInfo& server : servers) {
            // Проверка доступности сервера
            QNetworkAccessManager* manager = new QNetworkAccessManager();
            QNetworkRequest request(QUrl(server.url + "/health"));
            
            QNetworkReply* reply = manager->get(request);
            
            connect(reply, &QNetworkReply::finished, [=]() {
                bool wasAvailable = server.available;
                server.available = (reply->error() == QNetworkReply::NoError);
                server.lastCheck = QDateTime::currentDateTime();
                
                if (wasAvailable != server.available) {
                    emit serverAvailabilityChanged(server.url, 
                                                  server.available);
                }
                
                reply->deleteLater();
                manager->deleteLater();
            });
        }
    }
    
signals:
    void serverAvailabilityChanged(const QString& url, bool available);
};

class ScalableService : public QObject {
    Q_OBJECT
    
private:
    LoadBalancer* loadBalancer;
    QThreadPool* threadPool;
    
public:
    ScalableService(QObject* parent = nullptr) : QObject(parent) {
        loadBalancer = new LoadBalancer(this);
        threadPool = new QThreadPool(this);
        threadPool->setMaxThreadCount(10);
    }
    
    void processRequest(const QJsonObject& request) {
        // Выбор сервера
        QString serverUrl = loadBalancer->selectServer();
        if (serverUrl.isEmpty()) {
            emit errorOccurred("Нет доступных серверов");
            return;
        }
        
        // Асинхронная обработка в пуле потоков
        QtConcurrent::run(threadPool, [=]() {
            JsonRpcClient client(serverUrl);
            
            QEventLoop loop;
            QJsonObject result;
            bool success = false;
            
            connect(&client, &JsonRpcClient::methodSuccess,
                    [&](const QJsonValue& response) {
                result["data"] = response;
                success = true;
                loop.quit();
            });
            
            connect(&client, &JsonRpcClient::methodError,
                    [&](const QJsonObject& error) {
                result["error"] = error;
                loop.quit();
            });
            
            client.callMethod("process", QJsonArray{request});
            loop.exec();
            
            emit requestProcessed(result, success);
        });
    }
    
signals:
    void requestProcessed(const QJsonObject& result, bool success);
    void errorOccurred(const QString& error);
};
Концепция распределенной обработки данных
Программирование сетевых приложений

Заключение

Ключевые концепции распределенных систем

  • Прозрачность — скрытие сложности распределенной природы
  • Масштабируемость — возможность роста системы
  • Отказоустойчивость — продолжение работы при сбоях
  • Согласованность — поддержание целостности данных
  • Безопасность — защита данных и коммуникаций

Современные технологии

  • gRPC — высокопроизводительный RPC от Google
  • Apache Thrift — кросс-платформенный RPC фреймворк
  • Message Queue — асинхронные системы обмена сообщениями
  • Microservices — архитектурный стиль распределенных приложений
Концепция распределенной обработки данных
Программирование сетевых приложений

Инструменты Qt для распределенных систем

  • Qt Network — низкоуровневые сетевые операции
  • Qt WebSockets — двунаправленная связь в реальном времени
  • Qt HTTP — высокоуровневые HTTP операции
  • Qt JSON — сериализация данных
  • Qt Concurrent — параллельные вычисления
Концепция распределенной обработки данных
Программирование сетевых приложений

Вопросы для самопроверки

  1. Чем отличается RPC от REST архитектуры?
  2. Какие преимущества и недостатки у JSON-RPC по сравнению с XML-RPC?
  3. Как реализовать безопасность в распределенных системах?
  4. Что такое прозрачность в контексте распределенных систем?
  5. Какие механизмы обеспечивают отказоустойчивость?
  6. Как реализовать балансировку нагрузки в распределенной системе?
  7. Какие протоколы используются для удаленного вызова процедур?
  8. Как обеспечить согласованность данных в распределенной системе?
  9. Чем отличается клиент-серверная архитектура от P2P?
  10. Какие современные технологии используются для создания распределенных приложений?
Концепция распределенной обработки данных
Программирование сетевых приложений

Резюме

Основные достижения

  • Разобрали архитектуру распределенных систем
  • Изучили протоколы удаленного вызова процедур
  • Реализовали примеры на C++ и Qt
  • Рассмотрели вопросы безопасности и масштабируемости
  • Изучили методы публикации и развертывания служб
Концепция распределенной обработки данных
Программирование сетевых приложений

Дальнейшее изучение

  • gRPC и Protocol Buffers
  • Apache Thrift
  • Message Queue системы (RabbitMQ, Apache Kafka)
  • Микросервисная архитектура
  • Контейнеризация и оркестрация (Docker, Kubernetes)
  • Облачные технологии (AWS, Azure, Google Cloud)

Qt предоставляет мощные инструменты для создания распределенных приложений, объединяя простоту разработки с высокой производительностью.

Концепция распределенной обработки данных

Заметки докладчика: - Это итоговая теоретическая лекция курса — объединяет все предыдущие темы: сети, ООП, полиморфизм, исключения, потоки, GUI - Лабораторная работа: удалённый калькулятор на основе RPC (JSON-RPC или XML-RPC) - Лекция очень длинная — возможно потребуется 2 занятия - Рекомендуемое распределение времени: 1. Понятие распределённых систем и архитектуры — 15 мин 2. RPC, XML-RPC, JSON-RPC с примерами кода — 25 мин 3. Удалённые объекты (ООП + RPC) — 15 мин 4. Web Services (SOAP vs REST) — 15 мин 5. Безопасность (JWT, SSL/TLS) — 15 мин 6. Развертывание, мониторинг, балансировка нагрузки — 15 мин - Если время ограничено: SOAP и мониторинг можно сократить, балансировку — упомянуть обзорно

Заметки докладчика: - Ключевые свойства: автономность (узлы независимы), гетерогенность (разные платформы/ОС/языки), масштабируемость - Теорема CAP: невозможно одновременно обеспечить все три: Consistency (согласованность), Availability (доступность), Partition tolerance (устойчивость к разделению) - Пример для обсуждения: чат-приложение — сервер хранит историю сообщений, несколько клиентов подключаются - Если сервер падает — клиенты теряют связь (partition tolerance) - Если два сервера не синхронизированы — разные клиенты видят разную историю (consistency) - Архитектурные стили: клиент-сервер (централизованный), P2P (децентрализованный), многоуровневая (3-tier) - Обратить внимание на то, что распределённая система выглядит как единая система для пользователя

Заметки докладчика: - Клиент-сервер проще контролировать и защищать: есть центральная точка авторизации, логирования и обновления. - Минус клиент-серверной модели — центральный сервер становится потенциальной точкой отказа и узким местом производительности. - P2P повышает живучесть, но усложняет согласованность данных, поиск узлов, маршрутизацию и безопасность. - Многоуровневая архитектура часто используется в бизнес-приложениях: UI-клиент обращается к API, API обращается к БД и внешним сервисам. - Связать с RPC: RPC обычно применяют между уровнями системы, когда один компонент должен вызвать операцию другого компонента.

Заметки докладчика: - RPC — это не один конкретный протокол, а подход к построению взаимодействия между программами. - Главное обещание RPC: скрыть детали сети за привычной моделью вызова функции. - Важно сразу предупредить: полная прозрачность невозможна, потому что сеть медленнее памяти и может отказывать. - Пример из жизни: клиент вызывает add(5, 3), но фактически происходит сериализация, отправка по сети, обработка на сервере и получение ответа. - Подчеркнуть различие: локальный вызов обычно надежен и быстрый, удаленный вызов всегда связан с задержкой, таймаутом и возможной недоступностью сервера.

Заметки докладчика: - RPC особенно полезен там, где клиенту нужна не просто передача данных, а выполнение конкретной операции: calculate(), getUser(), createOrder(). - В корпоративных системах RPC часто используется между внутренними сервисами, потому что там важен строгий контракт и предсказуемая схема вызовов. - Для студентов: удаленный калькулятор — учебная миниатюра реальной схемы, где клиент не считает сам, а делегирует операцию серверу. - Можно сравнить с библиотекой: локальная библиотека подключается в процесс, а RPC-сервис расположен в другом процессе или на другом компьютере.

Заметки докладчика: - Нарисовать на доске цепочку: client -> client stub -> transport -> server stub -> real method. - Stub можно объяснить как адаптер между обычным вызовом функции и сетевым сообщением. - Dispatcher на сервере похож на таблицу маршрутов: по строке "calculator.add" выбирается нужная функция. - Маршаллинг нужен из-за того, что указатели, ссылки и объекты памяти нельзя напрямую передать в другой процесс. - В Qt роль транспорта могут выполнять QTcpSocket, QNetworkAccessManager, WebSocket или другой сетевой механизм.

Заметки докладчика: - Контракт — центральная идея RPC: клиент должен знать, какие методы существуют и какие параметры допустимы. - В gRPC контракт описывается в .proto-файлах, в SOAP — через WSDL, в Thrift — через IDL. - JSON-RPC и XML-RPC могут работать без отдельного IDL, но контракт все равно существует хотя бы в документации. - Обратить внимание на версионирование: изменение параметров метода может сломать старых клиентов. - Хорошая практика: не удалять и не менять смысл существующих полей без необходимости; добавлять новые поля совместимо.

Заметки докладчика: - Простое различие: RPC спрашивает "какую функцию вызвать?", REST спрашивает "с каким ресурсом работаем?". - RPC удобен для команд и операций: calculateTax, generateReport, reserveTicket. - REST удобен для CRUD-модели: получить пользователя, создать заказ, обновить профиль. - В реальных системах подходы часто смешиваются: публичное API может быть REST, а внутренние сервисы общаются через gRPC. - Не утверждать, что один подход всегда лучше: выбор зависит от предметной области, требований к производительности и удобства сопровождения.

Заметки докладчика: - Это один из самых важных теоретических моментов RPC: ошибка сети не говорит, выполнилась ли операция на сервере. - Пример: клиент отправил "списать 100 рублей", сервер списал деньги, но ответ потерялся. Повтор запроса может списать деньги второй раз. - Идемпотентность означает, что повторный вызов не меняет результат сверх первого выполнения. Например, setStatus("paid") безопаснее, чем chargeCard(100). - JSON-RPC поле id помогает сопоставлять ответ с запросом, но само по себе не решает проблему повторного выполнения. - Exactly once в распределенных системах обычно требует хранения состояния, транзакций, журналов и дедупликации; на практике часто проектируют операции под at least once или at most once.

Заметки докладчика: - Частая ошибка начинающих: считать любой сбой "ошибкой сервера". На практике нужно понимать уровень, на котором произошел сбой. - Таймаут — это не доказательство, что операция не выполнилась. - Для демонстрации можно спросить: какие операции безопасно повторять? getBalance — обычно да, transferMoney — нет без idempotency key. - В JSON-RPC ошибки возвращаются в поле error, а транспортные ошибки приходят через HTTP/сокет. - В Qt сетевые ошибки удобно получать через QNetworkReply::error() или сигналы QAbstractSocket.

Заметки докладчика: - В Qt нельзя надолго блокировать главный поток GUI: окно перестанет отвечать. - Синхронный стиль проще для учебного примера, но в реальных приложениях часто используют асинхронность. - Асинхронность усложняет код: нужно хранить состояние запроса, сопоставлять ответы по id, обрабатывать отмену и таймауты. - Можно связать с предыдущими темами курса: event loop, сигналы и слоты, многопоточность.

Заметки докладчика: - Хорошая фраза для запоминания: "RPC выглядит как обычный вызов функции, но ведет себя как сеть". - Не скрывать от студентов сложность: абстракция полезна, но опасна, если забыть про таймауты, повторы и частичные отказы. - Привести пример: локальная функция add() выполняется микросекунды, а удаленная add() может занять десятки миллисекунд из-за сети. - В производительном коде нельзя делать сотни мелких RPC-вызовов в цикле, если можно отправить один пакетный запрос.

Заметки докладчика: - RPC скрывает сетевую сложность — клиент вызывает удалённую функцию как локальную - Паттерн Proxy: stub на клиенте маршаллизует параметры, отправляет по сети, серверный stub демаршаллизует и вызывает реальную функцию - XML-RPC: более старый, тяжёлый формат (вербальный XML), но строгая типизация - JSON-RPC: современный, лёгкий, лучше читаемость, проще интеграция с JavaScript - Ключевые понятия: marshalling (сериализация) и unmarshalling (десериализация) - Проблемы RPC: задержки сети, потери пакетов, разные типы данных на клиенте/сервере - В Qt: QNetworkAccessManager + сигналы/слоты для асинхронных вызовов - CalculatorProxy — пример паттерна Proxy из лекций по ООП (виртуальные функции, наследование)

Заметки докладчика: - XML-RPC использует HTTP как транспортный протокол (POST-запросы) - В Qt: QNetworkAccessManager для HTTP-запросов клиента, QXmlStreamReader/QXmlStreamWriter для парсинга XML - Серверная реализация: QTcpServer + реестр методов (QMap с именами методов и лямбдами/функторами) - std::function<QVariant(QVariantList)> — тип для обработчиков методов - Клиент сериализует параметры в XML, отправляет POST, парсит XML-ответ - Типы данных XML-RPC: int, boolean, string, double, dateTime.iso8601, base64, array, struct - Ограничения: verbose формат, медленный парсинг XML, но строгая типизация и широкая поддержка - В продакшене: валидация входящего XML, обработка ошибок (faultCode/faultString), timeout - Попросить студентов сравнить размер XML-RPC и JSON-RPC запросов визуально

Заметки докладчика: - JSON-RPC 2.0 — современный стандарт, описан на jsonrpc.org/specification - Формат запроса: {"jsonrpc":"2.0","method":"...","params":[...],"id":1} - Поля: jsonrpc (версия), method (имя метода), params (массив или объект), id (для корреляции запроса и ответа) - Ответ: содержит "result" при успехе или "error" при ошибке, плюс "id" для сопоставления - Уведомления: запрос без id — сервер не присылает ответ - Qt имеет встроенную поддержку JSON: QJsonDocument, QJsonObject, QJsonArray, QJsonValue - Значительно проще XML-RPC — меньше кода, быстрее парсинг, легче отладка - QJsonDocument::fromJson() и toJson() — основные методы сериализации/десериализации - В продакшене: валидация параметров, обработка ошибок, timeout для запросов

Заметки докладчика: - gRPC можно представить как "современный промышленный RPC": контракт, генерация кода, бинарная сериализация, HTTP/2. - Protocol Buffers компактнее JSON и XML, потому что передают бинарное представление, а не текст с именами тегов. - HTTP/2 дает мультиплексирование: несколько запросов могут идти по одному соединению без ожидания друг друга. - Streaming в gRPC бывает серверный, клиентский и двунаправленный; это полезно для телеметрии, чатов и потоковой обработки. - REST чаще удобнее для публичных веб-API, а gRPC — для внутреннего взаимодействия сервисов, где обе стороны контролируются разработчиками одной системы. - Apache Thrift похож по идее: IDL -> генерация кода -> вызовы между языками, но исторически использует другой стек.

Заметки докладчика: - Удаленный метод — развитие идеи RPC для объектно-ориентированного программирования. - Вместо вызова свободной функции calculator.add() клиент вызывает метод объекта calculator->add(). - Важно различать объект и его прокси: прокси живет в клиентском процессе, настоящий объект — на сервере. - Сложность удаленных объектов выше, потому что появляется состояние, жизненный цикл объекта, удаление, ссылки и права доступа. - Исторические примеры: Java RMI, CORBA, DCOM. Современные системы чаще используют более простые сервисные контракты.

Заметки докладчика: - SOAP: XML-based, строгая схема (WSDL), тяжелый, много overhead, но мощная типизация и стандарты безопасности (WS-Security) - REST: HTTP-методы (GET/POST/PUT/DELETE), безсессионный (stateless), JSON, простой и легковесный - Индустриальный тренд: REST доминирует в новых разработках, SOAP остаётся в enterprise/банковской сфере - В Qt: QNetworkAccessManager — основной класс для REST-клиентов - REST сервер в примере: маршрутизация по QRegularExpression — упрощённый роутер - Упомянуть GraphQL как альтернативу REST (один endpoint, клиент выбирает нужные поля) - WSDL — контракт SOAP-сервиса, описывает доступные методы и типы данных - REST использует HTTP-коды статуса (200, 201, 400, 401, 404, 500) для передачи результата

Заметки докладчика: - JWT (JSON Web Token) используется для аутентификации в REST API - Структура JWT: header.payload.signature (все части в base64) - Header описывает алгоритм (HS256), payload содержит данные (userId, exp), signature — подпись - Важно: payload НЕ зашифрован, только подписан — не храните секреты в payload - В продакшене используйте готовые библиотеки (не кастомную криптографию, как в примере) - Пример с XOR — только для демонстрации концепции, не использовать в реальных системах! - SSL/TLS через QSslSocket обязателен для production-серверов - Обратить внимание на обработку sslErrors — в продакшене нужно проверять сертификаты, а не игнорировать - SSL-сертификаты: самоподписанные для разработки, Let's Encrypt для продакшена

Заметки докладчика: - Балансировка нагрузки распределяет запросы между несколькими экземплярами сервера - Алгоритмы: round-robin (по кругу), least connections (наименьшее число соединений), hash-based (по хэшу ключа) - Наш пример использует least-load — выбираем сервер с минимальной текущей нагрузкой - В продакшене: nginx, HAProxy, Traefik, облачные балансировщики (AWS ALB) - Health checks — периодическая проверка доступности серверов (в примере каждые 30 секунд) - Это продвинутый материал — хорошая тема для курсового проекта - ScalableService использует QThreadPool + QtConcurrent для асинхронной обработки - Обратить внимание на QEventLoop внутри QtConcurrent::run — позволяет ждать асинхронный результат

Заметки докладчика: - Ожидаемые ответы: 1. RPC: вызов процедур на удалённом сервере, прозрачно для клиента; REST: работа с ресурсами через HTTP-методы (GET/POST/PUT/DELETE), безсессионный, основан на представлениях. 2. JSON-RPC: компактнее, быстрее парсится, лучше читаемость, проще интеграция с JS; XML-RPC: строгая типизация, встроенная поддержка сложных типов, больше overhead. 3. Аутентификация (JWT), авторизация (RBAC/ACL), шифрование (SSL/TLS, AES), валидация входных данных, rate limiting. 4. Скрытие сложности распределённой природы от пользователя/разработчика — клиент не знает, что вызов выполняется удалённо. 5. Дублирование данных/сервисов, балансировка нагрузки, health checks, автоматический failover, репликация. 6. Round-robin, наименьшее количество соединений, хэш-балансировка, взвешенная балансировка. Пример из лекции: least-load. 7. XML-RPC, JSON-RPC, gRPC (Protocol Buffers), SOAP, CORBA (устаревший), Java RMI. 8. ACID-транзакции (в реляционных БД), eventual consistency, кворум (система распределённого консенсуса: Raft, Paxos), версионирование данных. 9. Клиент-сервер: централизованный сервер, чёткие роли; P2P: все узлы равноправны, нет единого центра, выше отказоустойчивость. 10. gRPC, Apache Thrift, RabbitMQ/Kafka (message queues), Docker/Kubernetes, микросервисы, WebSockets, REST/GraphQL.